package com.example.yuanboot.common.config.mybatis;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.example.yuanboot.common.anno.UserInterception;
import com.example.yuanboot.common.enums.config.DataHandlerEnum;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Properties;
import java.util.stream.Collectors;

/**
 * 数据权限拦截器(基于Mybatis)
 *
 * @author qingyan 2021-07-14
 */
@Slf4j
    @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
@RequiredArgsConstructor
public class DataPermissionInterceptor implements Interceptor {

    private DataInterceptor<Object> dataInterceptor;

    private String column;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if(log.isInfoEnabled()){
            log.info("进入 PrepareInterceptor 拦截器...");
        }
        Object target = getTarget(invocation.getTarget());
        if(target instanceof RoutingStatementHandler) {
            RoutingStatementHandler handler = (RoutingStatementHandler) target;
            StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");
            //通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
            MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
            //千万不能用下面注释的这个方法，会造成对象丢失，以致转换失败
            //MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            UserInterception userInterception = getPermissionByDelegate(mappedStatement);
            if(userInterception == null){
                if(log.isInfoEnabled()){
                    log.info("数据权限放行...");
                }
                return invocation.proceed();
            }
            if(log.isInfoEnabled()){
                log.info("数据权限处理【拼接SQL】...");
            }
            BoundSql boundSql = delegate.getBoundSql();
            ReflectUtil.setFieldValue(boundSql, "sql", permissionSql(boundSql.getSql(), userInterception));
        }
        return invocation.proceed();
    }

    /**
     * 权限sql包装
     * @author GaoYuan
     * @date 2018/4/17 上午9:51
     */
    protected String permissionSql(String sql, UserInterception userInterception) {
        if (userInterception.level().equals(DataHandlerEnum.ALL)) {
            return sql;
        }
        StringBuffer sbf = new StringBuffer();
        if (StrUtil.containsIgnoreCase(sql,"count")) {
            if (StrUtil.containsIgnoreCase(sql, "where")) {
                sql = sql.replaceAll("(?i)(where)?", "where %s and");
                sbf.append(sql);
            }else {
                sbf.append(sql).append(" where %s");
            }
        } else {
            String limit = "";
            if (StrUtil.containsIgnoreCase(sql, "limit")) {
                int index = StrUtil.indexOfIgnoreCase(sql, "limit");
                limit = sql.substring(index);
                sql = sql.replace(limit, "");
            }
            sbf.append("select * from (")
                    .append(sql)
                    .append(") permission_sql ")
                    .append("where %s ")
                    .append(limit);
        }
        sql = sbf.toString();
        switch (userInterception.level()) {
            case NONE:
               return String.format(sql, "1='2'");
            case ONE:
                String one = dataInterceptor.one().toString();
                return String.format(sql, column + "='" + one + "'");
            case MANY:
                String collect = dataInterceptor.many().stream().map(s -> "'" + s + "'")
                        .collect(Collectors.joining(",", "(", ")"));
                return String.format(sql, column + " in " + collect);
            default:
                return null;
        }

    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }


    private UserInterception getPermissionByDelegate(MappedStatement mappedStatement) {
        UserInterception anno = null;
        try {
            String mappedStatementId = mappedStatement.getId();
            String className = mappedStatementId.substring(0, mappedStatementId.lastIndexOf("."));
            String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);
            final Class<?> cls = Class.forName(className);
            final Method[] methods = cls.getMethods();
            for (Method method : methods) {
                if (method.getName().equals(methodName) && (anno = method.getAnnotation(UserInterception.class)) != null) {
                    return anno;
                }
            }
            if ((anno = cls.getAnnotation(UserInterception.class)) != null) {
                return anno;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return anno;
    }

    @SneakyThrows
    private Object getTarget(Object proxy){
        Field field = proxy.getClass().getSuperclass().getDeclaredField("h");
        field.setAccessible(true);
        Object target = field.get(proxy);
        //获取指定对象中此字段的值
        field = target.getClass().getDeclaredField("target");
        field.setAccessible(true);
        return field.get(target);
    }

    public void setPropertes(DataInterceptor dataInterceptor, String column) {
        this.column = column;
        this.dataInterceptor = dataInterceptor;
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
